using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using GamePlayConfig;
using UnityEngine;

namespace Net
{
    internal class WebSocketClient 
    {
        private ClientWebSocket _client = new ClientWebSocket();
        
        private const int ReceiveBufferSize = 1024;
        private const int SendBufferSize = 1024; 

        internal WebSocketClient()
        {
            _receiveBuffer = new byte[ReceiveBufferSize];
            _receiveBufferSegment = new ArraySegment<byte>(_receiveBuffer);
        }

        private readonly byte[] _receiveBuffer;
        private readonly ArraySegment<byte> _receiveBufferSegment;
        private static readonly object _receiveLock = new object();
        private readonly List<byte> _dynamicBuffer = new List<byte>();



        public async void StartConnect()
        {
            await Connect("ws://124.222.49.98:8080/ws");
        }

        private async Task Connect(string uri)
        {
            try
            {
                await _client.ConnectAsync(new Uri(uri), CancellationToken.None);
    #if UNITY_EDITOR
                Debug.Log("Connected to server");
    #endif
                // Start a new thread to receive messages
                Task.Run(Receive).ContinueWith(t =>
                {
                    if (t.IsFaulted)
                    {
                        Debug.LogError("Error in Receive task: " + t.Exception);
                    }
                });
            }
            catch (WebSocketException ex)
            {
                Debug.LogError("Failed to connect to server: " + ex.Message);
                if (ex.InnerException != null)
                {
                    Debug.LogError("Inner exception: " + ex.InnerException.Message);
                }

                CloseConnect();
            }
        }
        
        public void CloseConnect()
        {
            if (_client.State == WebSocketState.Open)
            {
                _client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
            }
        }
        
        public async Task SendBytesAsync(byte[] data)
        {
            int offset = 0;
            while (offset < data.Length)
            {
                int count = Math.Min(SendBufferSize, data.Length - offset);
                var segment = new ArraySegment<byte>(data, offset, count);
                bool endOfMessage = (offset + count >= data.Length);
                await _client.SendAsync(segment, WebSocketMessageType.Binary, endOfMessage, CancellationToken.None);
                offset += count;
            }
        }
        
        public void SendCmdMessageAsync(BaseMessage message)
        {
            var cmd = MessageFactory.Create<cmd>();
            cmd.id = message.MsgId;
            cmd.data = message.Serialize();
            cmd.sign = "xx";
            cmd.code = 0;
            SendBytesAsync(cmd.Serialize());
        }

        private async Task Receive()
        {
            while (true)
            {
                _dynamicBuffer.Clear();
                WebSocketReceiveResult result;
                do
                {
                    result = await _client.ReceiveAsync(_receiveBufferSegment, CancellationToken.None);
                    _dynamicBuffer.AddRange(_receiveBuffer.Take(result.Count));
                } while (!result.EndOfMessage);
                
                if (result.MessageType == WebSocketMessageType.Binary)
                {
                    var message = DecodeCmdMessage(_dynamicBuffer.ToArray());
                    if (message != null)
                    {
                        NetWorkManager.Instance.EnqueueMessage(message);
                    }
                }
                else if (result.MessageType == WebSocketMessageType.Close)
                {
                    await _client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
                    break;
                }
            }
        }

        private BaseMessage DecodeMessage(byte[] buffer)
        {
            int offset = 0;
            return MessageDecoder.Decode(buffer,ref offset);
        }
        
        private BaseMessage DecodeCmdMessage(byte[] buffer)
        {
            // ProtoHelper.PrintBytes(buffer);
            int offset = 0;
            var cmd = MessageFactory.Create<cmd>();
            cmd.Deserialize(buffer, ref offset);
            if (cmd.code != 0 && cmd.code != 200)
            {
                UnityMainThreadDispatcher.Instance.Enqueue(() =>
                {
                    var message = "Error code: " + cmd.code;
                    var errcodeCfg = ErrorCodeCfg.Get(cmd.code);
                    if (errcodeCfg != null)
                    {
                        message += " " + errcodeCfg.ZH_CN;
                    }
                    Debug.LogError(message);
                });
                return null;
            }
            if (cmd.data != null && cmd.data.Length > 0)
            {
                return DecodeMessage(cmd.data);
            }

            return null;
        }
        
        public bool IsConnected()
        {
            return _client.State == WebSocketState.Open;
        }

    }
    
}

